#include "udpServer.hpp"
#include "onlineUser.hpp"

#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>

using namespace std;
using namespace Server;

static void Usage(string proc){
    cerr << "\nUsage:\n\t" << proc << " local_port\n\n";
}


// demo1 字典雏形小demo
/////////////////////////////////////////////////////////////
const std::string dictTxt = "./dict.txt";
unordered_map<string, string> dict;

// 初始化函数 initDict 需要用到的一个辅助函数，帮忙把 dict.txt 里免面的每一条用 ":" 隔开的字符串分割一下，好 insert 进 dict
bool cutString(const string& target, string* key, string* value, const string& sep){
    // apple:苹果
    auto pos = target.find(sep);
    // 下面的这一行代码是不行的哦，因为这个 target 不是 unordered_map，而是一个 string，pos != string::npos
    // if(pos == target.end()) return false;
    if(pos == string::npos) return false;

    *key = target.substr(0, pos);
    *value = target.substr(pos + sep.size());
    return true;
}

// 初始化 dict
static void initDict(){
    ifstream in(dictTxt, std::ios::binary);
    if(!in.is_open()){
        cerr << "open file " << dictTxt << " error" <<endl;
        exit(OPEN_ERR);
    }
    string line;
    std::string key, value;
    while(getline(in, line)){
        // cout << lien << endl; 
        // key 和 value 是作为输出型参数得传递出来的
        if(cutString(line, &key, &value, ":") == true)
            dict.insert(make_pair(key, value));
    }

    in.close();

    cout << "load dict success" << endl;
}

// 下面的函数是捕捉 ctr + c 信号，自动加载更新 dict 的哦
// 这样就不用手动再次退出更新来了
void reload(int signo){
    (void)signo;
    initDict();
}

// 打印看一下 dict 有没有事自己想要的 dict --> 看字典是否有问题
static void DeBugPrint(){
    cout << "dict.size() = " << dict.size() << endl;
    for(const auto& dt: dict)
        cout << dt.first << " # " << dt.second << endl;
}

// client 通过sendto出来的message,我们服务器得通过dict字典发送回去client想要的结果
void handlerMessage(int sockfd, const string& clientip, const uint16_t& clientport, const string& message){
    // 1. 直接在 dict 里面去找
    auto pos = dict.find(message);
    string response_message = "";
    bool flags = true;
    if(pos == dict.end()){ // 没有找到
        flags = false;
        response_message = "字典里面没有找到哦,可能是没有加入进dict里面吧!";
    }//else response_message = dict[message];  // 有找到的情况
    else response_message = pos->second;

    // 服务端可以直接打印client端需要查找的单词，当然也可以用下面 的代码把信息发送给 client
    // 那么client得创建一个子线程用来接收server端发送过来的数据
    // if(flags == true)
    //     cout << "你要查找的单词：" << message << " 的意思是：" << response_message << endl;
    // else
    //     cout << "你要查找的单词：" << message << " 我们的字典里面还没有记录！" << endl;

    // 2. 再sendto给client
    struct sockaddr_in client;
    memset(&client, 0, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_addr.s_addr = inet_addr(clientip.c_str()); 
    client.sin_port = htons(clientport);
    
    // // 因为 sendto 函数需要 sockfd 所以要求回调函数得传入 server 端的 sockfd 用来和 client 端通信
    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

// demo2 让server执行一些指令
////////////////////////////////////////////////////////////////
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd){
    // 1. cmd 解析，ls -a -l
    // 2. 如果有必要可能得 fork, exec*

    if(cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("mkdir") != string::npos){
        cerr << clientip << ":" << clientport << "  正在执行非法操作: " << cmd << endl;
        return;
    }

    string response;
    FILE* fp = popen(cmd.c_str(), "r");
    if(fp == nullptr) response = cmd + " exec failed";
    char line[1024];
    while(fgets(line, sizeof(line), fp)){
        response += line;
    }
    pclose(fp);

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_addr.s_addr = inet_addr(clientip.c_str());  // c_str() 是把string转化为char*的
    client.sin_port = htons(clientport);

    // 发送给client的子线程的
    sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}

// demo3 聊天室小雏形
//////////////////////////////////////////////////////////////////////
OnlineUser onlineuser;

// client 看聊天室里面的人发送来的是什么信息
void routeMessage(int sockfd, const string& clientip, const uint16_t& clientport, const string& message){
    if(message == "online")  // 他想上线
        onlineuser.addUser(clientip, clientport);
    if(message == "offline") // 他想下线
        onlineuser.delUser(clientip, clientport);
    
    // 他在发送信息，如果他在线上，就把他发送的信息route到群里的每一个人
    if(onlineuser.isOnline(clientip, clientport) == true)
        onlineuser.broadcastMessage(sockfd, clientip, clientport, message);
    else{  // 不在向上还发送信息，就提醒他先上线
        // sendto
        struct sockaddr_in client;
        bzero(&client, sizeof(client));
        client.sin_family = AF_INET;
        client.sin_addr.s_addr = inet_addr(clientip.c_str());
        client.sin_port = htons(clientport);

        string s = "你还没有上线,请先上线,online";

        sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr*)&client, sizeof(client));
    }
}

// ./udpServer port
int main(int argc, char* argv[]){
    if(argc != 2){
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);

    // demo1 字典雏形小demo
    // initDict();
    // signal(2, reload);
    // // DeBugPrint();
    // unique_ptr<udpServer> usr(new udpServer(handlerMessage, port));
    // usr->initServer();
    // usr->start();

    // demo2 让server执行一些指令
    // unique_ptr<udpServer> usr(new udpServer(execCommand, port));
    // usr->initServer();
    // usr->start();

    // demo3 聊天室小雏形
    unique_ptr<udpServer> usr(new udpServer(routeMessage, port));
    usr->initServer();
    usr->start();

    return 0;
}

